package cn.yll.common.utils;

import cn.yll.common.exception.BsErrEnum;
import cn.yll.common.exception.BsException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;

import javax.crypto.*;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.DESedeKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

/**
 * 加解密工具类
 * @date 2022-05-30
 * @author yangll
 */
@Slf4j
public class EncryptUtil {
    public static final String MD5 = "MD5";
    public static final String SHA1 = "SHA1";
    public static final String HmacMD5 = "HmacMD5";
    public static final String HmacSHA1 = "HmacSHA1";
    /**
     * 第一代DES加密算法
     */
    public static final String DES = "DES";
    /**
     * 更安全的DES加密算法
     */
    public static final String DESede = "DESede";
    public static final String CIPHER_AES = "AES/ECB/PKCS5Padding";
    public static final String CIPHER_DES = "DES/ECB/PKCS5Padding";
    public static final String CIPHER_DESEDE = "DESede/ECB/PKCS5Padding";
    /** 加密/解密算法名称 比DES更高级更安全的加密算法 */
    public static final String AES = "AES";

    /** 随机数生成器（RNG）算法名称 */
    public static final String RNG_ALGORITHM = "SHA1PRNG";
    /** 密钥长度: 128, 192 or 256 */
    private static final int KEY_SIZE = 128;

    public static final Charset UTF_8 = StandardCharsets.UTF_8;

    /**
     * 使用MessageDigest进行单向加密（无密码）
     * @param res 被加密的文本
     * @param algorithm 加密算法名称
     * @return
     */
    private static String messageDigest(String res, String algorithm){
        try {
            MessageDigest md = MessageDigest.getInstance(algorithm);
            byte[] resBytes = res.getBytes(UTF_8);
            return Base64Encode(md.digest(resBytes));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 使用KeyGenerator进行单向/双向加密（可设密码）
     * @param res 被加密的原文
     * @param algorithm  加密使用的算法名称
     * @param key 加密使用的秘钥
     * @return
     */
    private static String keyGeneratorMac(String res,String algorithm,String key){
        try {
            SecretKey sk;
            if (key==null) {
                KeyGenerator kg = KeyGenerator.getInstance(algorithm);
                sk = kg.generateKey();
            }else {
                byte[] keyBytes = key.getBytes(UTF_8);
                sk = new SecretKeySpec(keyBytes, algorithm);
            }
            Mac mac = Mac.getInstance(algorithm);
            mac.init(sk);
            return Base64Encode(mac.doFinal(res.getBytes(UTF_8)));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 使用KeyGenerator双向加密，DES/AES，注意这里转化为字符串的时候是将2进制转为16进制格式的字符串，不是直接转，因为会出错
     * @param res 加密的原文
     * @param algorithm 加密使用的算法名称
     * @param key  加密的秘钥
     * @param keysize
     * @param isEncode
     * @return
     */
    private static String keyGeneratorES(String res, String algorithm, String key, int keysize, boolean isEncode){
        if (!StringUtils.hasText(res)) {
            return null;
        }
        try {
            KeyGenerator kg = KeyGenerator.getInstance(algorithm);
            if (keysize == 0) {
                byte[] keyBytes = key.getBytes(UTF_8);
                kg.init(new SecureRandom(keyBytes));
            }else if (key==null) {
                kg.init(keysize);
            }else {
                byte[] keyBytes = key.getBytes(UTF_8);
                kg.init(keysize, new SecureRandom(keyBytes));
            }
            SecretKey sk = kg.generateKey();
            SecretKeySpec sks = new SecretKeySpec(sk.getEncoded(), algorithm);
            Cipher cipher = Cipher.getInstance(algorithm);
            if (isEncode) {
                cipher.init(Cipher.ENCRYPT_MODE, sks);
                byte[] resBytes = res.getBytes(UTF_8);
                return parseByte2HexStr(cipher.doFinal(resBytes));
            }else {
                cipher.init(Cipher.DECRYPT_MODE, sks);
                byte[] bytes = parseHexStr2Byte(res);
                return new String(cipher.doFinal(bytes));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**将二进制转换成16进制 */
    public static String parseByte2HexStr(byte buf[]) {
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < buf.length; i++) {
            String hex = Integer.toHexString(buf[i] & 0xFF);
            if (hex.length() == 1) {
                hex = '0' + hex;
            }
            sb.append(hex.toUpperCase());
        }
        return sb.toString();
    }

    /**将16进制转换为二进制*/
    public static byte[] parseHexStr2Byte(String hexStr) {
        if (hexStr.length() < 1) {
            return null;
        }
        byte[] result = new byte[hexStr.length()/2];
        for (int i = 0;i< hexStr.length()/2; i++) {
            int high = Integer.parseInt(hexStr.substring(i * 2, i * 2 + 1), 16);
            int low = Integer.parseInt(hexStr.substring(i * 2 + 1, i * 2 + 2), 16);
            result[i] = (byte) (high * 16 + low);
        }
        return result;
    }

    /**
     * 使用Base64进行加密
     * @param res 要转码的字串
     * @return 转码后的字串
     */
    public static String Base64Encode(String res) {
        return Base64Encode(res.getBytes(UTF_8));
    }

    /**
     * 对目标进行base64转码
     * @param res 转码字符串byte数组
     * @return 转码后的字串
     */
    private static String Base64Encode(byte[] res){
        return Base64.getEncoder().encodeToString(res);
    }

    /**
     * 使用Base64进行解密
     * @param res 加密的字符串
     * @return 解密后的字符串
     */
    public static String Base64Decoder(String res) {
        return new String(Base64Decode(res));
    }

    /**
     * 使用Base64进行解密
     * @param res 转码后的字串
     * @return byte数组
     */
    private static byte[] Base64Decode(String res) {
        return Base64.getDecoder().decode(res.getBytes(UTF_8));
    }

    /**
     * 使用Base64进行加密
     * @param res 要转码的字串
     * @return 转码后的字串
     */
    public static String Base64UrlEncode(String res) {
        return Base64UrlEncode(res.getBytes(UTF_8));
    }

    /**
     * 对目标进行base64转码
     * @param res 转码字符串byte数组
     * @return 转码后的字串
     */
    private static String Base64UrlEncode(byte[] res){
        return Base64.getUrlEncoder().encodeToString(res);
    }

    /**
     * 使用Base64进行解密
     * @param res 加密的字符串
     * @return 解密后的字符串
     */
    public static String Base64UrlDecoder(String res) {
        return new String(Base64UrlDecode(res));
    }

    /**
     * 使用Base64进行解密
     * @param res 转码后的字串
     * @return byte数组
     */
    private static byte[] Base64UrlDecode(String res) {
        return Base64.getDecoder().decode(res.getBytes(UTF_8));
    }

    /**
     * md5加密算法进行加密（不可逆）
     * @param res 需要加密的原文
     * @return
     */
    public static String MD5(String res) {
        return messageDigest(res, MD5);
    }

    /**
     * md5加密算法进行加密（不可逆）
     * @param res  需要加密的原文
     * @param key  秘钥
     * @return
     */
    public static String HmacMD5(String res, String key) {
        return keyGeneratorMac(res, HmacMD5, key);
    }

    /**
     * 使用SHA1加密算法进行加密（不可逆）
     * @param res 需要加密的原文
     * @return
     */
    public static String SHA1(String res) {
        return messageDigest(res, SHA1);
    }

    /**
     * 使用SHA1加密算法进行加密（不可逆）
     * @param res 需要加密的原文
     * @param key 秘钥
     * @return
     */
    public static String SHA1(String res, String key) {
        return keyGeneratorMac(res, HmacSHA1, key);
    }

//    public static void main(String[] args) {
//        String ss = "hjfdsahuifs";
//        System.out.println("MD5_BASE64===" + MD5(ss));
//        System.out.println("HMAC-MD5_BASE64===" + HmacMD5(ss,"jf142slafdsa"));
//        System.out.println("SHA1 no Key_BASE64===" + SHA1(ss));
//        System.out.println("SHA1_BASE64===" + SHA1(ss, "jf142slafdsa"));
//    }

    /**
     * 使用DES加密算法进行加密（可逆）
     * @param res 需要加密的原文
     * @param key 长度8位以上且是8的整数倍
     * @return
     */
    public static String DESEncode(String res, String key) {
        try {
            //生成密钥
            SecretKey k = initDESKey(key);
            //实例化
            Cipher cipher=Cipher.getInstance(CIPHER_DES);
            //初始化，设置为加密模式
            cipher.init(Cipher.ENCRYPT_MODE, k);
            //执行操作
            return Base64Encode(cipher.doFinal(res.getBytes(UTF_8)));
        } catch (Exception e) {
            log.error("DES加密异常", e);
            throw new BsException(BsErrEnum.SYSTEM_ERROR);
        }
    }

    /**
     * 生成密钥
     */
    private static SecretKey initDESKey(String key) throws Exception {
        //实例化Des密钥
        DESKeySpec dks=new DESKeySpec(key.getBytes(UTF_8));
        //实例化密钥工厂
        SecretKeyFactory keyFactory=SecretKeyFactory.getInstance(DES);
        //生成密钥
        return keyFactory.generateSecret(dks);
    }

    /**
     * 对使用DES加密算法的密文进行解密（可逆）
     * @param res 需要解密的密文
     * @param key 秘钥 长度8位以上且是8的整数倍
     * @return
     */
    public static String DESDecode(String res, String key) {
        try {
            //生成密钥
            SecretKey k = initDESKey(key);
            //实例化
            Cipher cipher=Cipher.getInstance(CIPHER_DES);
            //初始化，设置为解密模式
            cipher.init(Cipher.DECRYPT_MODE, k);
            //执行操作
            return new String(cipher.doFinal(Base64Decode(res)));
        } catch (Exception e) {
            log.error("DES解密异常", e);
            throw new BsException(BsErrEnum.SYSTEM_ERROR);
        }
    }

    /**
     * 使用DESede加密算法进行加密（可逆）（更安全的加密算法）
     * @param res 需要加密的原文
     * @param key 秘钥 长度24位以上且是8的整数倍
     * @return 加密后的字串
     */
    public static String DESedeEncode(String res, String key) {
        try {
            //生成密钥
            SecretKey k = initDESedeKey(key);
            //实例化
            Cipher cipher=Cipher.getInstance(CIPHER_DESEDE);
            //初始化，设置为加密模式
            cipher.init(Cipher.ENCRYPT_MODE, k);
            //执行操作
            return Base64Encode(cipher.doFinal(res.getBytes(UTF_8)));
        } catch (Exception e) {
            log.error("DESede加密异常", e);
            throw new BsException(BsErrEnum.SYSTEM_ERROR);
        }
    }

    /**
     * 生成密钥
     */
    private static SecretKey initDESedeKey(String key) throws Exception {
        //实例化Des密钥
        DESedeKeySpec dks = new DESedeKeySpec(key.getBytes(UTF_8));
        //实例化密钥工厂
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(DESede);
        //生成密钥
        return keyFactory.generateSecret(dks);
    }

    /**
     * 对使用DES加密算法的密文进行解密（可逆）（更安全的加密算法）
     * @param res 需要解密的密文
     * @param key 秘钥 长度24位以上且是8的整数倍
     * @return 解密后的字串
     */
    public static String DESedeDecode(String res, String key) {
        try {
            //生成密钥
            SecretKey k = initDESedeKey(key);
            //实例化
            Cipher cipher = Cipher.getInstance(CIPHER_DESEDE);
            //初始化，设置为解密模式
            cipher.init(Cipher.DECRYPT_MODE, k);
            //执行操作
            return new String(cipher.doFinal(Base64Decode(res)));
        } catch (Exception e) {
            log.error("DESede解密异常", e);
            throw new BsException(BsErrEnum.SYSTEM_ERROR);
        }
    }

    /**
     * 使用AES加密算法经行加密（可逆）
     * @param res 需要加密的密文
     * @param key 秘钥
     * @return
     */
    public static String AESEncode(String res, String key) {
        try {
            Cipher cipher = Cipher.getInstance(CIPHER_AES);
            cipher.init(Cipher.ENCRYPT_MODE, generateKey(key.getBytes(UTF_8)));
            return Base64Encode(cipher.doFinal(res.getBytes(UTF_8)));
        } catch (Exception e) {
            log.error("AES加密异常：", e);
            throw new BsException(BsErrEnum.SYSTEM_ERROR);
        }
    }

    /**
     * 对使用AES加密算法的密文进行解密
     * @param res 需要解密的密文
     * @param key 秘钥
     * @return
     */
    public static String AESDecode(String res, String key) {
        try {
            Cipher cipher = Cipher.getInstance(CIPHER_AES);
            cipher.init(Cipher.DECRYPT_MODE, generateKey(key.getBytes(UTF_8)));
            byte[] result = cipher.doFinal(Base64Decode(res));
            return new String(result, UTF_8);
        } catch (Exception e) {
            log.error("AES解密异常：", e);
            throw new BsException(BsErrEnum.SYSTEM_ERROR);
        }
    }

    /**
     * 生成密钥
     */
    private static SecretKey generateKey(byte[] key) throws Exception {
        SecureRandom secureRandom = SecureRandom.getInstance(RNG_ALGORITHM);
        secureRandom.setSeed(key);
        KeyGenerator secretGenerator = KeyGenerator.getInstance(AES);
        secretGenerator.init(KEY_SIZE, secureRandom);
        return secretGenerator.generateKey();
    }

    /**
     * 使用异或进行加密
     * @param res 需要加密的密文
     * @param key 秘钥
     * @return
     */
    public static String XORencode(String res, String key) {
        byte[] bs = res.getBytes(UTF_8);
        for (int i = 0; i < bs.length; i++) {
            bs[i] = (byte) ((bs[i]) ^ key.hashCode());
        }
        return parseByte2HexStr(bs);
    }

    /**
     * 使用异或进行解密
     * @param res 需要解密的密文
     * @param key 秘钥
     * @return
     */
    public static String XORdecode(String res, String key) {
        byte[] bs = parseHexStr2Byte(res);
        for (int i = 0; i < bs.length; i++) {
            bs[i] = (byte) ((bs[i]) ^ key.hashCode());
        }
        return new String(bs);
    }

    /**
     * 数字异或加解密
     * 直接使用异或（第一调用加密，第二次调用解密）
     * @param res 密文
     * @param key 秘钥
     * @return
     */
    public static int XOR(int res, String key) {
        return res ^ key.hashCode();
    }

    private static final String RSA_KEY_ALGORITHM = "RSA";
    private static final String RSA_PUBLIC_KEY = "public";
    private static final String RSA_PRIVATE_KEY = "private";
    private static final String RSA_SIGNATURE_ALGORITHM = "SHA1WithRSA";

    /**
     * 创建RSA公私钥
     */
    public static Map<String, Key> initRSAKey() {
        KeyPairGenerator keyPairGenerator = null;
        try {
            keyPairGenerator = KeyPairGenerator.getInstance(RSA_KEY_ALGORITHM);
        } catch (NoSuchAlgorithmException e) {
            log.error("创建RSA密钥生成器异常", e);

            throw new BsException(BsErrEnum.SYSTEM_ERROR, "创建RSA密钥生成器异常");
        }
        keyPairGenerator.initialize(1024);
        KeyPair keyPair = keyPairGenerator.generateKeyPair();
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        Map<String, Key> keyMap = new HashMap<>(2);
        keyMap.put(RSA_PUBLIC_KEY, publicKey);
        keyMap.put(RSA_PRIVATE_KEY, privateKey);
        return keyMap;
    }

    /**
     * 根据Base64编码的公钥字符串获取公钥
     * @param pubKeyStr Base64编码的公钥字符串
     */
    public static PublicKey getRSAPublicKey(String pubKeyStr) {
        byte[] keyBytes = Base64Decode(pubKeyStr);
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
        try {
            KeyFactory keyFactory = KeyFactory.getInstance(RSA_KEY_ALGORITHM);
            return keyFactory.generatePublic(keySpec);
        } catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
            throw new BsException(BsErrEnum.SYSTEM_ERROR, "获取RSA公钥异常", e);
        }
    }

    /**
     * 根据Base64编码的私钥字符串获取私钥
     * @param priKeyStr Base64编码的私钥字符串
     */
    public static PrivateKey getRSAPrivateKey(String priKeyStr) {
        byte[] keyBytes = Base64Decode(priKeyStr);
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
        try {
            KeyFactory keyFactory = KeyFactory.getInstance(RSA_KEY_ALGORITHM);
            return keyFactory.generatePrivate(keySpec);
        } catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
            throw new BsException(BsErrEnum.SYSTEM_ERROR, "获取RSA私钥异常", e);
        }
    }

    /**
     * 通过私钥签名文本内容
     * @param content 原明文
     * @param priKey 私钥字符串
     */
    public static String signByPrivateKey(String content, String priKey) {
        try {
            PrivateKey privateKey = getRSAPrivateKey(priKey);
            Signature signature = Signature.getInstance(RSA_SIGNATURE_ALGORITHM);
            signature.initSign(privateKey);
            signature.update(content.getBytes(UTF_8));
            byte[] bytes = signature.sign();
            return Base64UrlEncode(bytes);
        } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) {
            throw new BsException(BsErrEnum.SYSTEM_ERROR, "获取RSA私钥异常", e);
        }
    }

    /**
     * 通过公钥验签
     * @param content 验签内容
     * @param sign  签名
     * @param publicKey 公钥
     * @return 验签结果
     */
    public static boolean verifySignByPublicKey(String content, String sign, String publicKey) {
        try {
            PublicKey pubKey = getRSAPublicKey(publicKey);
            Signature signature = Signature.getInstance(RSA_SIGNATURE_ALGORITHM);
            signature.initVerify(pubKey);
            signature.update(content.getBytes(UTF_8));
            return signature.verify(Base64Decode(sign));
        } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) {
            throw new BsException(BsErrEnum.SYSTEM_ERROR, "通过公钥验签异常", e);
        }
    }

    /**
     * 通过公钥加密
     * @param plainText 明文
     * @param publicKey 公钥
     * @return 密文
     */
    public static String encryptByPublicKey(String plainText, String publicKey) {
        try {
            PublicKey pubKey = getRSAPublicKey(publicKey);
            Cipher cipher = Cipher.getInstance(RSA_KEY_ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, pubKey);
            byte[] enBytes = cipher.doFinal(plainText.getBytes(UTF_8));
            return Base64Encode(enBytes);
        } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException |
                 BadPaddingException e) {
            throw new BsException(BsErrEnum.SYSTEM_ERROR, "通过公钥加密异常", e);
        }
    }

    /**
     * 通过私钥解密
     * @param enStr 加密字符串
     * @param privateKey 私钥
     * @return 明文
     */
    public static String decryptByPrivateKey(String enStr, String privateKey) {
        try {
            PrivateKey priKey = getRSAPrivateKey(privateKey);
            Cipher cipher = Cipher.getInstance(RSA_KEY_ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, priKey);
            byte[] deBytes = cipher.doFinal(Base64Decode(enStr));
            return new String(deBytes);
        } catch (Exception e) {
            throw new BsException(BsErrEnum.SYSTEM_ERROR, "通过公钥验签异常", e);
        }
    }

//    public static void main(String[] args) {
//        String userName = "400:1212.3434/Tonelink";
//        String rUserName = userName.replace("/", "%25").replace(":", "%3A");
//        String enUserName = EncryptUtil.Base64Encode(rUserName);
//        System.out.println("原始userName:" + userName + ", 先字符替换:" + rUserName + ", 再base64加密：" + enUserName);
//
//        String password = "96GNB56L45";
//        String enPassword = EncryptUtil.Base64Encode(password);
//        System.out.println("原始password:" + password + ", base64加密：" + enPassword);
//
//        String nonce = "Z8d/CNlaEtCgByhNUZkt2g==";
//        String sign = enPassword + nonce;
//        System.out.println("once和conce串联：" + sign);
//        sign = EncryptUtil.SHA1(sign);
//        System.out.println("签名：" + sign);
//    }

//    public static void main(String[] args) {
//        String encryptionContent = "待加密数据";
//        // 第一步，生成一对公私钥
//        Map<String, Key> keys = new HashMap<>(2);
//        try {
//            keys = initRSAKey();
//        }catch (Exception e){
//            log.error("init RSA key error，message is {}", e.getMessage());
//            System.exit(-1);
//        }
//        // 获得公钥
//        Key publicKey = keys.get(RSA_PUBLIC_KEY);
//        String base64PublicKeyStr = Base64Encode(publicKey.getEncoded());
//        log.info("base64 publicKey String is:{}", base64PublicKeyStr);
//        // 加密
//        String encryptionString = encryptByPublicKey(encryptionContent, base64PublicKeyStr);
//        log.info("encryptionString by publicKey ,encryptionString is: {}", encryptionString);
//        // 解密
//        // 获得私钥
//        Key privateKey = keys.get(RSA_PRIVATE_KEY);
//        // 私钥Base64编码字符串
//        String base64PrivateKeyStr = Base64Encode(privateKey.getEncoded());
//        log.info("base64 privateKey String is:{}", base64PrivateKeyStr);
//        String decryptionString = decryptByPrivateKey(encryptionString, base64PrivateKeyStr);
//        log.info("decryptionString by privateKey ,decryptionString is: {}", decryptionString);
//    }
}
